<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/all.min.css" integrity="sha256-mUZM63G8m73Mcidfrv5E+Y61y7a12O5mW4ezU3bxqW4=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"pengyz.com","root":"/","images":"/images","scheme":"Pisces","version":"8.7.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":false,"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>
<meta name="description" content="1. 重构版Launcher的技术方案简单的对比多厂商Launcher本身是一个使用QT作为界面库，CEF作为内嵌浏览器，使用C++语言进行开发的桌面程序。多厂商Launcher存在很多的问题，在决定重构之后，对后续的技术方案进行了一番考量，具体的对比如下：     多厂商 重构版    Qt版本 5.6.3 5.12.6   CEF版本 76.0.3809.162 (支持XP+) 86.0.40">
<meta property="og:type" content="article">
<meta property="og:title" content="重构版Launcher开发实践01：CEF篇">
<meta property="og:url" content="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/index.html">
<meta property="og:site_name" content="Alex的技术博客">
<meta property="og:description" content="1. 重构版Launcher的技术方案简单的对比多厂商Launcher本身是一个使用QT作为界面库，CEF作为内嵌浏览器，使用C++语言进行开发的桌面程序。多厂商Launcher存在很多的问题，在决定重构之后，对后续的技术方案进行了一番考量，具体的对比如下：     多厂商 重构版    Qt版本 5.6.3 5.12.6   CEF版本 76.0.3809.162 (支持XP+) 86.0.40">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-11-28-19-26-55.png">
<meta property="og:image" content="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-12-02-11-51-03.png">
<meta property="og:image" content="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-11-30-09-47-26.png">
<meta property="article:published_time" content="2020-11-26T16:48:05.000Z">
<meta property="article:modified_time" content="2021-09-16T03:51:06.089Z">
<meta property="article:author" content="Alex Peng">
<meta property="article:tag" content="C++">
<meta property="article:tag" content="CEF">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-11-28-19-26-55.png">


<link rel="canonical" href="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/","path":"2020/11/27/重构版Launcher开发实践：CEF篇/","title":"重构版Launcher开发实践01：CEF篇"}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>重构版Launcher开发实践01：CEF篇 | Alex的技术博客</title>
  




  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
<link rel="alternate" href="/atom.xml" title="Alex的技术博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">Alex的技术博客</h1>
      <i class="logo-line"></i>
    </a>
      <p class="site-subtitle" itemprop="description">不忘初心，方得始终</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu">
        <li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li>
        <li class="menu-item menu-item-about"><a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a></li>
        <li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li>
        <li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li>
        <li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</div>
        
  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>

  <aside class="sidebar">

    <div class="sidebar-inner sidebar-nav-active sidebar-toc-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
            <div class="post-toc animated"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#1-%E9%87%8D%E6%9E%84%E7%89%88Launcher%E7%9A%84%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88"><span class="nav-number">1.</span> <span class="nav-text">1. 重构版Launcher的技术方案</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%AE%80%E5%8D%95%E7%9A%84%E5%AF%B9%E6%AF%94"><span class="nav-number">1.1.</span> <span class="nav-text">简单的对比</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%86%85%E5%B5%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E9%9C%80%E8%A6%81%E5%AE%8C%E6%88%90%E7%9A%84%E5%8A%9F%E8%83%BD"><span class="nav-number">1.2.</span> <span class="nav-text">内嵌浏览器需要完成的功能</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#2-CEF%E4%BB%8B%E7%BB%8D"><span class="nav-number">2.</span> <span class="nav-text">2. CEF介绍</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%83%8C%E6%99%AF"><span class="nav-number">2.1.</span> <span class="nav-text">背景</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BE%9D%E8%B5%96"><span class="nav-number">2.2.</span> <span class="nav-text">依赖</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#CEF3%E5%AE%9E%E7%8E%B0%E7%BB%86%E8%8A%82"><span class="nav-number">2.3.</span> <span class="nav-text">CEF3实现细节</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#3-C-%E5%92%8CJavaScript%E7%9A%84%E4%BA%92%E8%B0%83%E7%94%A8"><span class="nav-number">3.</span> <span class="nav-text">3 C++和JavaScript的互调用</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#C-%E8%B0%83%E7%94%A8JavaScript"><span class="nav-number">3.1.</span> <span class="nav-text">C++调用JavaScript</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#JavaScript%E8%B0%83%E7%94%A8C"><span class="nav-number">3.2.</span> <span class="nav-text">JavaScript调用C++</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-CefV8Handler%E6%8E%A5%E5%8F%A3"><span class="nav-number">3.2.1.</span> <span class="nav-text">1. CefV8Handler接口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-%E5%AE%9E%E7%8E%B0CefV8Handler%E6%8E%A5%E5%8F%A3"><span class="nav-number">3.2.2.</span> <span class="nav-text">2. 实现CefV8Handler接口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-%E8%B0%83%E7%94%A8V8%E6%8E%A5%E5%8F%A3%E5%88%9B%E5%BB%BANative-JavaScript%E5%87%BD%E6%95%B0"><span class="nav-number">3.2.3.</span> <span class="nav-text">3. 调用V8接口创建Native JavaScript函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4-%E5%9C%A8%E6%8E%A7%E5%88%B6%E5%8F%B0%E6%89%A7%E8%A1%8C%E5%A6%82%E4%B8%8BJavaScript%E5%87%BD%E6%95%B0"><span class="nav-number">3.2.4.</span> <span class="nav-text">4. 在控制台执行如下JavaScript函数</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#4-%E5%81%B7%E8%AF%A5%E5%81%B7%E7%9A%84%E6%87%92%EF%BC%9A%E8%87%AA%E5%8A%A8%E7%BB%91%E5%AE%9A%E7%9A%84%E7%BC%98%E7%94%B1%E5%92%8C%E6%9E%84%E6%83%B3"><span class="nav-number">4.</span> <span class="nav-text">4. 偷该偷的懒：自动绑定的缘由和构想</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#4-1-%E5%8A%A8%E6%9C%BA%EF%BC%9A%E6%89%8B%E5%8A%A8%E6%B3%A8%E5%86%8C%E7%9A%84%E5%BC%8A%E7%AB%AF"><span class="nav-number">4.1.</span> <span class="nav-text">4.1 动机：手动注册的弊端</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-2-%E5%8F%AF%E8%A1%8C%E6%80%A7%EF%BC%9AIPC%E8%B0%83%E7%94%A8%E5%92%8C%E6%9C%AC%E5%9C%B0%E8%B0%83%E7%94%A8%E7%9A%84%E7%AD%89%E4%BB%B7%E8%BD%AC%E6%8D%A2"><span class="nav-number">4.2.</span> <span class="nav-text">4.2 可行性：IPC调用和本地调用的等价转换</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-3-%E8%A7%A3%E5%86%B3%E6%80%9D%E8%B7%AF%EF%BC%9A%E5%9F%BA%E4%BA%8EQt%E5%8F%8D%E5%B0%84%E5%AE%9E%E7%8E%B0%E8%87%AA%E5%8A%A8%E6%B3%A8%E5%86%8C"><span class="nav-number">4.3.</span> <span class="nav-text">4.3 解决思路：基于Qt反射实现自动注册</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#5-%E5%AE%9E%E7%8E%B0%E7%BB%86%E8%8A%82"><span class="nav-number">5.</span> <span class="nav-text">5. 实现细节</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#5-1-%E6%B3%A8%E5%86%8C%E5%92%8C%E7%BB%91%E5%AE%9A"><span class="nav-number">5.1.</span> <span class="nav-text">5.1 注册和绑定</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-browser%E8%BF%9B%E7%A8%8B%E4%B8%AD%E4%BB%A5%E6%8C%87%E5%AE%9A%E7%9A%84%E5%90%8D%E5%AD%97%E6%B3%A8%E5%86%8C%E4%B8%80%E4%B8%AAQObject%E7%9A%84%E5%AD%90%E7%B1%BB%E5%AF%B9%E8%B1%A1"><span class="nav-number">5.1.1.</span> <span class="nav-text">1. browser进程中以指定的名字注册一个QObject的子类对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-%E6%B3%A8%E5%86%8C%E7%BB%86%E8%8A%82%EF%BC%9A%E9%81%8D%E5%8E%86%E5%85%83%E5%AF%B9%E8%B1%A1%EF%BC%8C%E8%8E%B7%E5%8F%96%E5%87%BD%E6%95%B0%E7%AD%BE%E5%90%8D%E4%BF%A1%E6%81%AF%EF%BC%8C%E5%86%99%E5%85%A5%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98"><span class="nav-number">5.1.2.</span> <span class="nav-text">2. 注册细节：遍历元对象，获取函数签名信息，写入共享内存</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-render%E8%BF%9B%E7%A8%8B%E5%88%9D%E5%A7%8B%E5%8C%96%E6%97%B6%E8%A7%A3%E6%9E%90Json%E4%BF%A1%E6%81%AF%E5%B9%B6%E5%AE%8C%E6%88%90%E6%B3%A8%E5%86%8C"><span class="nav-number">5.1.3.</span> <span class="nav-text">3. render进程初始化时解析Json信息并完成注册</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4-%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA%E6%97%B6%EF%BC%88OnContextCreated%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%EF%BC%89%EF%BC%8C%E6%B3%A8%E5%86%8C%E6%89%80%E6%9C%89%E7%9A%84C-%E5%87%BD%E6%95%B0"><span class="nav-number">5.1.4.</span> <span class="nav-text">4. 浏览器对象创建时（OnContextCreated接口调用），注册所有的C++函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#5-%E6%B3%A8%E5%86%8C%E7%BB%86%E8%8A%82%EF%BC%9A"><span class="nav-number">5.1.5.</span> <span class="nav-text">5. 注册细节：</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#6-QCefFunctionObject-Execute%E5%AE%9E%E7%8E%B0"><span class="nav-number">5.1.6.</span> <span class="nav-text">6. QCefFunctionObject::Execute实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#7-browser%E8%BF%9B%E7%A8%8B%E6%94%B6%E5%88%B0%E8%B0%83%E7%94%A8%E8%AF%B7%E6%B1%82%E5%90%8E%E7%9A%84%E5%A4%84%E7%90%86"><span class="nav-number">5.1.7.</span> <span class="nav-text">7. browser进程收到调用请求后的处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#8-Qt%E5%8F%8D%E5%B0%84%E8%B0%83%E7%94%A8"><span class="nav-number">5.1.8.</span> <span class="nav-text">8 Qt反射调用</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-2-%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0%E8%A7%A6%E5%8F%91%E6%9C%BA%E5%88%B6"><span class="nav-number">5.2.</span> <span class="nav-text">5.2 回调函数触发机制</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-Execute%E5%87%BD%E6%95%B0%E4%B8%AD%E5%AF%B9%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0%E7%9A%84%E5%A4%84%E7%90%86"><span class="nav-number">5.2.1.</span> <span class="nav-text">1 Execute函数中对回调函数的处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-browser%E8%BF%9B%E7%A8%8BIPC%E8%A7%A6%E5%8F%91%E5%9B%9E%E8%B0%83"><span class="nav-number">5.2.2.</span> <span class="nav-text">2 browser进程IPC触发回调</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-render%E8%BF%9B%E7%A8%8B%E5%93%8D%E5%BA%94%E4%BB%A3%E7%A0%81"><span class="nav-number">5.2.3.</span> <span class="nav-text">3 render进程响应代码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4-%E6%89%A7%E8%A1%8C%E5%9B%9E%E8%B0%83"><span class="nav-number">5.2.4.</span> <span class="nav-text">4. 执行回调</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-3-%E5%9B%9E%E8%B0%83%E8%B5%84%E6%BA%90%E7%9A%84%E7%AE%A1%E7%90%86"><span class="nav-number">5.3.</span> <span class="nav-text">5.3 回调资源的管理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-4-%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8"><span class="nav-number">5.4.</span> <span class="nav-text">5.4 简单使用</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-%E5%AE%9A%E4%B9%89QObject%E7%9A%84%E5%AD%90%E7%B1%BB%EF%BC%8C%E9%80%9A%E8%BF%87Qt%E5%AE%8F%E5%B0%86%E8%A6%81%E5%AF%BC%E5%87%BA%E7%9A%84C-%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89%E4%B8%BA%E6%A7%BD%E5%87%BD%E6%95%B0"><span class="nav-number">5.4.1.</span> <span class="nav-text">1 定义QObject的子类，通过Qt宏将要导出的C++接口定义为槽函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-%E6%B3%A8%E5%86%8CQt%E5%AF%B9%E8%B1%A1"><span class="nav-number">5.4.2.</span> <span class="nav-text">2 注册Qt对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-%E6%B5%8B%E8%AF%95"><span class="nav-number">5.4.3.</span> <span class="nav-text">3 测试</span></a></li></ol></li></ol></li></ol></div>
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-overview">
            <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Alex Peng"
      src="/images/avata.png">
  <p class="site-author-name" itemprop="name">Alex Peng</p>
  <div class="site-description" itemprop="description">我的个人技术随笔</div>
</div>
<div class="site-state-wrap site-overview-item animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">15</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">17</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">15</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author site-overview-item animated">
      <span class="links-of-author-item">
        <a href="https://github.com/pengyz" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;pengyz" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:530948159@qq.com" title="E-Mail → mailto:530948159@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



          </div>
        </div>
      </div>
    </div>
  </aside>
  <div class="sidebar-dimmer"></div>


    </header>

    
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner post posts-expand">


  


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://pengyz.com/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avata.png">
      <meta itemprop="name" content="Alex Peng">
      <meta itemprop="description" content="我的个人技术随笔">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Alex的技术博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          重构版Launcher开发实践01：CEF篇
        </h1>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2020-11-27 00:48:05" itemprop="dateCreated datePublished" datetime="2020-11-27T00:48:05+08:00">2020-11-27</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2021-09-16 11:51:06" itemprop="dateModified" datetime="2021-09-16T11:51:06+08:00">2021-09-16</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%8A%80%E6%9C%AF/" itemprop="url" rel="index"><span itemprop="name">技术</span></a>
        </span>
          ，
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%8A%80%E6%9C%AF/CEF/" itemprop="url" rel="index"><span itemprop="name">CEF</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
        <h1 id="1-重构版Launcher的技术方案"><a href="#1-重构版Launcher的技术方案" class="headerlink" title="1. 重构版Launcher的技术方案"></a>1. 重构版Launcher的技术方案</h1><h2 id="简单的对比"><a href="#简单的对比" class="headerlink" title="简单的对比"></a>简单的对比</h2><p>多厂商Launcher本身是一个使用QT作为界面库，CEF作为内嵌浏览器，使用C++语言进行开发的桌面程序。多厂商Launcher存在很多的问题，在决定重构之后，对后续的技术方案进行了一番考量，具体的对比如下：</p>
<table>
<thead>
<tr>
<th></th>
<th>多厂商</th>
<th>重构版</th>
</tr>
</thead>
<tbody><tr>
<td>Qt版本</td>
<td>5.6.3</td>
<td>5.12.6</td>
</tr>
<tr>
<td>CEF版本</td>
<td>76.0.3809.162 (支持XP+)</td>
<td>86.0.4044.132 (支持win7+)</td>
</tr>
<tr>
<td>VS版本</td>
<td>VS 2013</td>
<td>VS 2017</td>
</tr>
<tr>
<td>构建系统</td>
<td>VS工程+CMake</td>
<td>CMake</td>
</tr>
<tr>
<td>版本升级方式</td>
<td>大网易的补丁升级系统</td>
<td>ngl-pacman工具</td>
</tr>
<tr>
<td>打包方式</td>
<td>bash脚本和cmd脚本</td>
<td>bash脚本</td>
</tr>
</tbody></table>
<span id="more"></span>

<p>下面是一些简单的说明：</p>
<ul>
<li>重构版Launcher不再支持xp，故Qt版本选用了最新的LTS版本，VS版本选用2017方便使用更新的C++标准。 </li>
<li>内嵌浏览器考虑过使用QtWebEngine，此模块是Qt官方对chromium进行的封装，可以方便地与Qt程序集成，但后续考虑到游戏内商城需要进行离屏渲染， QtWebEngine未开放相应接口，故放弃，继续使用CEF但对CEF版本进行了升级。</li>
<li>对工程结构进行了整理和简化，全部使用CMake进行构建，可以方便地跨平台，VS2017本身也对CMake提供了良好的支持。</li>
<li>大网易的补丁升级系统更适合对游戏进行升级，需要依次应用所有补丁，过于繁琐。创建了一个独立进程完成升级功能，可以跨版本进行升级。</li>
</ul>
<h2 id="内嵌浏览器需要完成的功能"><a href="#内嵌浏览器需要完成的功能" class="headerlink" title="内嵌浏览器需要完成的功能"></a>内嵌浏览器需要完成的功能</h2><p>Launcher本身是一个重Web的项目，开发过程中考虑过使用Electron来实现，但纯web技术栈做游戏Launcher存在不少的限制，安全性上也存在很大的挑战，最终走回了应用内嵌浏览器的解决方案。内嵌浏览器需要完成如下功能：</p>
<ul>
<li>提供浏览器环境，加载和显示网页。</li>
<li>将C++函数暴露给浏览器，提供底层数据存储，游戏下载，npl stub集成功能。</li>
<li>支持传入和触发JavaScript回调。</li>
<li>支持绑定native属性到JavaScript环境。</li>
<li>为网页提供跨页面数据访问和事件注册/触发功能。</li>
<li>提供离屏渲染功能用于显示游戏内商城。</li>
</ul>
<p>基于以上需求，重构版使用了一个开源的CEF QT封装层QCefView并对它进行了大量的改造，添加了基于Qt反射的函数和属性自动绑定层，对离屏渲染进行了实现。</p>
<h1 id="2-CEF介绍"><a href="#2-CEF介绍" class="headerlink" title="2. CEF介绍"></a>2. CEF介绍</h1><p>Chromium Embedded Framework (CEF)是一个将基于Chromium的浏览器嵌入到应用程序中的简单框架。</p>
<blockquote>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>The Chromium Embedded Framework (CEF)是一个由Marshall Greenblatt于2008年创建的开源项目，旨在开发基于Google Chromium项目的Web浏览器控件。CEF目前支持一系列编程语言和操作系统，可以很容易地集成到新的和现有的应用程序中。它从一开始就考虑到性能和易用性。基础框架包括通过本地库导出的C和C++编程接口，它们将宿主应用与Chromium和Blink的实现细节隔离。它提供浏览器控件和宿主应用程序之间的紧密集成，包括对自定义插件、协议、JavaScript对象和JavaScript扩展的支持。宿主应用程序可以选择性地控制资源加载、导航、上下文菜单、打印等，同时获得与Google Chrome浏览器相同的性能优势和HTML5技术支持。</p>
<h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><p>CEF项目依赖于由第三方维护的许多其他项目。CEF依赖的主要项目有：</p>
<ul>
<li>Chromium - 提供创建一个功能齐全的Web浏览器所需的网络堆栈、线程、消息循环、日志记录和进程控制等常规功能。实现允许Blink与V8和Skia通信的“平台”代码。许多Chromium设计文件可以在 <a target="_blank" rel="noopener" href="http://dev.chromium.org/developers">http://dev.chromium.org/developers</a> 找到。</li>
<li>Blink（以前叫WebKit）——Chromium使用的渲染实现。提供DOM解析、布局、事件处理、呈现和html5 Javascript API。一些HTML5实现被分散在Blink和Chromium代码库之间。</li>
<li>V8 - JavaScript 引擎.</li>
<li>Skia - 用于渲染非加速内容的二维图形库。关于Chrome是如何整合Skia的更多信息可以在 <em><a target="_blank" rel="noopener" href="http://www.chromium.org/developers/design-documents/graphics-and-skia">这里</a></em> 找到。</li>
<li>Angle - 为Windows实现的3D图形转换层，用于将GLES调用转换为DirectX调用。有关acclerated composing的更多信息，请访问 <em><a target="_blank" rel="noopener" href="http://dev.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome">此处</a></em> 。</li>
</ul>
<h2 id="CEF3实现细节"><a href="#CEF3实现细节" class="headerlink" title="CEF3实现细节"></a>CEF3实现细节</h2><p>自2013年1月以来，CEF3一直是CEF的推荐和支持版本。它通过Chromium Content API来使用与Chromium Web浏览器相同的多进程体架构。与使用单进程体架构的 CEF1（已废弃）相比，该体系结构具有许多优势：</p>
<ul>
<li>支持多进程运行模式</li>
<li>与Chromium浏览器共享更多代码</li>
<li>基于上一条，由于使用了“受支持”的代码路径，因此性能得到了改善，功能破坏次数更少。</li>
<li>更快地跟进Chromium更新以访问新功能<br>在大多数情况下，CEF3将具有与Chromium Web浏览器相同的性能和稳定性特性。</li>
</ul>
</blockquote>
<p>Chromium的多进程架构</p>
<blockquote>
<p><img src="/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-11-28-19-26-55.png"></p>
</blockquote>
<h1 id="3-C-和JavaScript的互调用"><a href="#3-C-和JavaScript的互调用" class="headerlink" title="3 C++和JavaScript的互调用"></a>3 C++和JavaScript的互调用</h1><h2 id="C-调用JavaScript"><a href="#C-调用JavaScript" class="headerlink" title="C++调用JavaScript"></a>C++调用JavaScript</h2><p>CEF提供了相应的接口可以直接使用</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">browser-&gt;<span class="built_in">GetMainFrame</span>()-&gt;<span class="built_in">ExecuteJavaScript</span>(<span class="string">&quot;__GDATA__.toogleLogin();&quot;</span>);</span><br></pre></td></tr></table></figure>

<h2 id="JavaScript调用C"><a href="#JavaScript调用C" class="headerlink" title="JavaScript调用C++"></a>JavaScript调用C++</h2><h3 id="1-CefV8Handler接口"><a href="#1-CefV8Handler接口" class="headerlink" title="1. CefV8Handler接口"></a>1. CefV8Handler接口</h3><p>CEF提供了标准的函数执行接口CefV8Handler，所有Native方法都必须实现此接口</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CefV8Handler</span> :</span> <span class="keyword">public</span> <span class="keyword">virtual</span> CefBaseRefCounted &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">//当JavaScript函数执行时会调用此接口</span></span><br><span class="line">  <span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">Execute</span><span class="params">(<span class="keyword">const</span> CefString&amp; name,</span></span></span><br><span class="line"><span class="params"><span class="function">                       CefRefPtr&lt;CefV8Value&gt; object,</span></span></span><br><span class="line"><span class="params"><span class="function">                       <span class="keyword">const</span> CefV8ValueList&amp; arguments,</span></span></span><br><span class="line"><span class="params"><span class="function">                       CefRefPtr&lt;CefV8Value&gt;&amp; retval,</span></span></span><br><span class="line"><span class="params"><span class="function">                       CefString&amp; exception)</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="2-实现CefV8Handler接口"><a href="#2-实现CefV8Handler接口" class="headerlink" title="2. 实现CefV8Handler接口"></a>2. 实现CefV8Handler接口</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//继承CefV8Handler</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QCefFunctionObject</span> :</span> <span class="keyword">public</span> CefV8Handler &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">Execute</span><span class="params">(<span class="keyword">const</span> CefString&amp; name, CefRefPtr&lt;CefV8Value&gt; object, <span class="keyword">const</span> CefV8ValueList&amp; arguments, CefRefPtr&lt;CefV8Value&gt;&amp; retval, CefString&amp; exception)</span> OVERRIDE</span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">      <span class="built_in">printf</span>(<span class="string">&quot;function: %s executed!\n&quot;</span>, name.<span class="built_in">toStdString</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    ....</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="3-调用V8接口创建Native-JavaScript函数"><a href="#3-调用V8接口创建Native-JavaScript函数" class="headerlink" title="3. 调用V8接口创建Native JavaScript函数"></a>3. 调用V8接口创建Native JavaScript函数</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建handler</span></span><br><span class="line">CefRefPtr&lt;QCefFunctionObject&gt; functionHandler = <span class="keyword">new</span> <span class="built_in">QCefFunctionObject</span>();</span><br><span class="line"><span class="comment">//通过handler创建函数对象</span></span><br><span class="line">CefRefPtr&lt;CefV8Value&gt; func = CefV8Value::<span class="built_in">CreateFunction</span>(funcInfo.name.<span class="built_in">toStdWString</span>(), functionHandler);</span><br><span class="line"><span class="comment">//获取页面全局window对象</span></span><br><span class="line">CefRefPtr&lt;CefV8Value&gt; objWindow = context-&gt;<span class="built_in">GetGlobal</span>();</span><br><span class="line"><span class="comment">//将函数对象插入到window对象中</span></span><br><span class="line">objWindow-&gt;<span class="built_in">SetValue</span>(<span class="string">&quot;test&quot;</span>, func, V8_PROPERTY_ATTRIBUTE_NONE);</span><br></pre></td></tr></table></figure>
<h3 id="4-在控制台执行如下JavaScript函数"><a href="#4-在控制台执行如下JavaScript函数" class="headerlink" title="4. 在控制台执行如下JavaScript函数"></a>4. 在控制台执行如下JavaScript函数</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.test();</span><br></pre></td></tr></table></figure>
<p>得到输出：function: test executed!</p>
<h1 id="4-偷该偷的懒：自动绑定的缘由和构想"><a href="#4-偷该偷的懒：自动绑定的缘由和构想" class="headerlink" title="4. 偷该偷的懒：自动绑定的缘由和构想"></a>4. 偷该偷的懒：自动绑定的缘由和构想</h1><p>Launcher中界面主要由web构建，如果按照MVC模式进行分层，Web处于View和Controller层，C++更多时候充当一个model层：提供游戏下载安装信息，stub功能集成，窗口的打开关闭控制等。C++通过注册Native函数到JavaScript中，为web提供增强功能，以便web更好地实现业务功能。<br>基于这种逻辑分层关系，随着业务需求的变更，C++经常要和web协商添加新的接口，注册新的函数到JavaScript中，所以简化这个注册流程是很有必要的，可以有效地降低开发工作量。</p>
<h2 id="4-1-动机：手动注册的弊端"><a href="#4-1-动机：手动注册的弊端" class="headerlink" title="4.1 动机：手动注册的弊端"></a>4.1 动机：手动注册的弊端</h2><p>如果我们手动来给每一个C++函数做绑定，我们需要在两个项目中同时添加代码，繁琐而且容易出错。<br>在Browser进程响应IPC消息的时候，我们的代码中必然会出现一个如下分派代码：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (functionName == <span class="string">&quot;getWebId&quot;</span>) &#123;</span><br><span class="line">  <span class="comment">//调用getWebId</span></span><br><span class="line">  <span class="keyword">auto</span> strWebId = obj.<span class="built_in">getWebId</span>();</span><br><span class="line">  ...</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (functionName == <span class="string">&quot;startDownload&quot;</span>) &#123;</span><br><span class="line">  <span class="comment">//调用startDownload</span></span><br><span class="line">  <span class="keyword">auto</span> gameVersion = params.<span class="built_in">GetString</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">auto</span> downloadPath=params.<span class="built_in">GetString</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">auto</span> result = obj.<span class="built_in">startDownload</span>(gameVersion, downloadPath);</span><br><span class="line">  ...</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (...) &#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>因为JavaScript是一种动态脚本，而C++是编译型强类型语言，因此收到IPC消息后要根据C++函数签名对参数数量和类型做合法性校验，写起来很繁琐。</p>
<p>在Render进程的CefV8Handler::Execute接口中，我们需要手动去拼接IPC消息，<br>每当添加一个新的JavaScript接口，都需要在此处添加新的序列化代码。这种重复性的工作最好能交给程序自动完成而不需要人工介入，这样就可以尽可能地简化工作量，提高开发效率。</p>
<p><strong>说了这么多，主要还是我们懒</strong></p>
<h2 id="4-2-可行性：IPC调用和本地调用的等价转换"><a href="#4-2-可行性：IPC调用和本地调用的等价转换" class="headerlink" title="4.2 可行性：IPC调用和本地调用的等价转换"></a>4.2 可行性：IPC调用和本地调用的等价转换</h2><p><strong>一个典型的JavaScript接口调用过程</strong><br><img src="/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-12-02-11-51-03.png"></p>
<p>可以看到，C++函数本身位于Browser进程（Launcher进程）中，JavaScript函数到额执行在render进程中，所以这里是一个典型的IPC跨进程调用过程。render进程中的函数只是一个stub，它仅用于将调用请求转发给browser进程。</p>
<p>当进行自动绑定的构想时，遇到的第一个问题就是：<br><strong>如果把JavaScript函数都变成了RPC调用，是否存在某些情况我们无法进行等价转换？</strong></p>
<p>实际情况是，RPC调用和本地调用在能力上讲是等价的，可以进行切换，调用者不会感知到一个调用到底是本地调用还是远程调用，RPC调用在web后端开发中已经大量应用。从软件设计上讲，软件应该是模块化的，高内聚，低耦合，必要时我们使用接口来进行模块间的通信，此时接口等价于一种消息。如果调用是跨进程的，使用IPC来进行进程间的通信，这两者没有什么本质区别，调用者并不关心这个调用是跨了“模块”还是垮了“进程”，我们可以把“进程”理解为模块概念的强化，进程间的资源是天然强制隔离的，通信必须完全依赖消息。</p>
<h2 id="4-3-解决思路：基于Qt反射实现自动注册"><a href="#4-3-解决思路：基于Qt反射实现自动注册" class="headerlink" title="4.3 解决思路：基于Qt反射实现自动注册"></a>4.3 解决思路：基于Qt反射实现自动注册</h2><p>CEF中C++函数调用是一个典型的IPC调用过程，render进程中仅存在一个stub函数，将函数调用序列化后通过IPC消息通知给browser进程，browser进程执行完真整的函数调用后，通过IPC消息将结果发送回render进程。<br>所以问题的关键是，如果我们能在render进程中拿到要注册的C++函数的完整函数签名（函数名，参数数量，参数类型），我们就可以以此为基础注册一个同名的JavaScript函数，当这个函数被调用时，将函数调用信息（函数名，参数等）序列化并通过CEF标准的IPC通信机制发送给browser进程。browser进程解析IPC消息，根据调用函数名查找函数元信息，校验调用参数并最终通过反射执行对应的C++函数，最后将执行结果通过IPC消息发回render进程，完成整个调用过程。</p>
<p>C++本身不支持反射但是Qt是支持的，可以在Qt元对象系统和反射的基础上实现自动绑定。</p>
<h1 id="5-实现细节"><a href="#5-实现细节" class="headerlink" title="5. 实现细节"></a>5. 实现细节</h1><h2 id="5-1-注册和绑定"><a href="#5-1-注册和绑定" class="headerlink" title="5.1 注册和绑定"></a>5.1 注册和绑定</h2><h3 id="1-browser进程中以指定的名字注册一个QObject的子类对象"><a href="#1-browser进程中以指定的名字注册一个QObject的子类对象" class="headerlink" title="1. browser进程中以指定的名字注册一个QObject的子类对象"></a>1. browser进程中以指定的名字注册一个QObject的子类对象</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;base&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectBase</span>(<span class="keyword">this</span>));</span><br></pre></td></tr></table></figure>
<h3 id="2-注册细节：遍历元对象，获取函数签名信息，写入共享内存"><a href="#2-注册细节：遍历元对象，获取函数签名信息，写入共享内存" class="headerlink" title="2. 注册细节：遍历元对象，获取函数签名信息，写入共享内存"></a>2. 注册细节：遍历元对象，获取函数签名信息，写入共享内存</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefObjectProtocol::registerJavaScriptHandlerObject</span><span class="params">(<span class="keyword">const</span> QString&amp; registerName, <span class="keyword">const</span> QObject* registerObject)</span> </span>&#123;</span><br><span class="line">  <span class="comment">//获取元对象</span></span><br><span class="line">  <span class="keyword">auto</span> metaObj = registerObject-&gt;<span class="built_in">metaObject</span>();</span><br><span class="line">  QJsonObject registerObjInfo;</span><br><span class="line">  registerObjInfo.<span class="built_in">insert</span>(<span class="string">&quot;registerName&quot;</span>, registerName);</span><br><span class="line">  registerObjInfo.<span class="built_in">insert</span>(<span class="string">&quot;className&quot;</span>, metaObj-&gt;<span class="built_in">className</span>());</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; metaObj-&gt;<span class="built_in">methodCount</span>(); i++) &#123;</span><br><span class="line">    <span class="comment">//获取函数名，函数签名，参数</span></span><br><span class="line">    functionInfo.<span class="built_in">insert</span>(<span class="string">&quot;functionName&quot;</span>, QString::<span class="built_in">fromUtf8</span>(metaMethod.<span class="built_in">name</span>()));</span><br><span class="line">    functionInfo.<span class="built_in">insert</span>(<span class="string">&quot;functionSignature&quot;</span>, QString::<span class="built_in">fromUtf8</span>(metaMethod.<span class="built_in">methodSignature</span>()));</span><br><span class="line">    functionInfo.<span class="built_in">insert</span>(<span class="string">&quot;returnType&quot;</span>, metaMethod.<span class="built_in">returnType</span>());</span><br><span class="line">    <span class="comment">//遍历参数列表</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; metaMethod.<span class="built_in">parameterCount</span>(); j++) &#123;</span><br><span class="line">      <span class="comment">//获取参类型名和参数名，添加到Json Array中</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//添加到Json Array</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//将序列化好的函数注册信息Json写入到共享内存</span></span><br><span class="line">  <span class="built_in">writeJsObjectRegisterInfo</span>(jsonStr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="3-render进程初始化时解析Json信息并完成注册"><a href="#3-render进程初始化时解析Json信息并完成注册" class="headerlink" title="3. render进程初始化时解析Json信息并完成注册"></a>3. render进程初始化时解析Json信息并完成注册</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefJavaScriptBinder::initRegisterObjectsData</span><span class="params">(<span class="keyword">const</span> QString&amp; jsonData)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//解析注册信息，将函数签名信息反序列化为结构体，最终以注册名为key保存在map里</span></span><br><span class="line">    ...</span><br><span class="line">    m_javaScriptMetaObjectMap[regName] = objectInfo;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="4-浏览器对象创建时（OnContextCreated接口调用），注册所有的C-函数"><a href="#4-浏览器对象创建时（OnContextCreated接口调用），注册所有的C-函数" class="headerlink" title="4. 浏览器对象创建时（OnContextCreated接口调用），注册所有的C++函数"></a>4. 浏览器对象创建时（OnContextCreated接口调用），注册所有的C++函数</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">QCefJavaScriptBinder::bindAllObjects</span><span class="params">(CefRefPtr&lt;CefV8Value&gt; parentObj, CefRefPtr&lt;CefBrowser&gt; browser, CefRefPtr&lt;CefFrame&gt; frame)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    QCefJavaScriptEnvironment* pJsEnv = <span class="keyword">new</span> <span class="built_in">QCefJavaScriptEnvironment</span>();</span><br><span class="line">    QStringList allRegisterNames = m_javaScriptMetaObjectMap.<span class="built_in">keys</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; regNameKey : allRegisterNames) &#123;</span><br><span class="line">        <span class="comment">//一些必要的校验判断</span></span><br><span class="line">        ...</span><br><span class="line">        JavaScriptMetaObject&amp; metaInfo = m_javaScriptMetaObjectMap[regNameKey];</span><br><span class="line">        CefRefPtr&lt;QCefJavaScriptObject&gt; jsObj = <span class="keyword">new</span> <span class="built_in">QCefJavaScriptObject</span>(metaInfo, browser, frame);</span><br><span class="line">        <span class="comment">//根据函数注册信息完成C++函数到JavaScript环境的自动注册</span></span><br><span class="line">        jsObj-&gt;<span class="built_in">registerObject</span>(pJsEnv, regNameKey.<span class="built_in">toStdWString</span>(), parentObj);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> pJsEnv;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="5-注册细节："><a href="#5-注册细节：" class="headerlink" title="5. 注册细节："></a>5. 注册细节：</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefJavaScriptObject::registerObject</span><span class="params">(QCefJavaScriptEnvironment* pJsEnv, CefString registerName, CefRefPtr&lt;CefV8Value&gt; cefParentObj)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//遍历找到正确的parent对象</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">//如果当前对象为空，创建它</span></span><br><span class="line">    <span class="keyword">if</span> (!currObjValue)</span><br><span class="line">        currObjValue = CefV8Value::<span class="built_in">CreateObject</span>(<span class="keyword">this</span>, <span class="literal">nullptr</span>);</span><br><span class="line">    <span class="comment">//遍历所有的function注册信息</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> JavaScriptMetaMethod&amp; funcInfo : m_metaObject.functions) &#123;</span><br><span class="line">        <span class="comment">//创建handler实例用于响应JavaScript函数调用</span></span><br><span class="line">        CefRefPtr&lt;QCefFunctionObject&gt; functionHandler = <span class="keyword">new</span> <span class="built_in">QCefFunctionObject</span>(funcInfo, m_browser, m_frame);</span><br><span class="line">        <span class="comment">//使用handler创建JavaScript函数，它是一个CefV8Value的实例</span></span><br><span class="line">        CefRefPtr&lt;CefV8Value&gt; func = CefV8Value::<span class="built_in">CreateFunction</span>(funcInfo.name.<span class="built_in">toStdWString</span>(), functionHandler);</span><br><span class="line">        <span class="comment">//将handler保存在map中，方便后续访问</span></span><br><span class="line">        m_functionMap.<span class="built_in">insert</span>(funcInfo.name, functionHandler);</span><br><span class="line">        <span class="comment">//将新创建的函数对象插入到当前JS对象中</span></span><br><span class="line">        currObjValue-&gt;<span class="built_in">SetValue</span>(funcInfo.name.<span class="built_in">toStdWString</span>(), func, V8_PROPERTY_ATTRIBUTE_NONE);</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="6-QCefFunctionObject-Execute实现"><a href="#6-QCefFunctionObject-Execute实现" class="headerlink" title="6. QCefFunctionObject::Execute实现"></a>6. QCefFunctionObject::Execute实现</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefFunctionObject::Execute</span><span class="params">(<span class="keyword">const</span> CefString&amp; name, CefRefPtr&lt;CefV8Value&gt; object,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">const</span> CefV8ValueList&amp; arguments, CefRefPtr&lt;CefV8Value&gt;&amp; retval, CefString&amp; exception)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//创建CEF标准的IPC消息，消息名为QCEF_INVOKENGLMETHOD</span></span><br><span class="line">    CefRefPtr&lt;CefProcessMessage&gt; msg = CefProcessMessage::<span class="built_in">Create</span>(QCEF_INVOKENGLMETHOD);</span><br><span class="line">    <span class="comment">//获取调用参数列表</span></span><br><span class="line">    CefRefPtr&lt;CefListValue&gt; args = msg-&gt;<span class="built_in">GetArgumentList</span>();</span><br><span class="line">    <span class="comment">//获取browserId和frameId，前者是浏览器内部唯一标识，后者是frame内部唯一标识</span></span><br><span class="line">    <span class="keyword">int</span> browserId = m_browser-&gt;<span class="built_in">GetIdentifier</span>();</span><br><span class="line">    int64 frameId = m_frame-&gt;<span class="built_in">GetIdentifier</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> idx = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//消息格式: browserId, frameId, C++类名，调用函数名，回调函数签名，参数列表</span></span><br><span class="line">    args-&gt;<span class="built_in">SetString</span>(idx++, QString::<span class="built_in">number</span>(browserId).<span class="built_in">toStdString</span>());</span><br><span class="line">    args-&gt;<span class="built_in">SetString</span>(idx++, QString::<span class="built_in">number</span>(frameId).<span class="built_in">toStdString</span>());</span><br><span class="line">    args-&gt;<span class="built_in">SetString</span>(idx++, m_metaMethod.className.<span class="built_in">toStdWString</span>());</span><br><span class="line">    args-&gt;<span class="built_in">SetString</span>(idx++, name);</span><br><span class="line">    args-&gt;<span class="built_in">SetString</span>(idx++, m_metaMethod.signature.<span class="built_in">toStdWString</span>());</span><br><span class="line">    <span class="comment">//check param count</span></span><br><span class="line">    QStringList signatureList;</span><br><span class="line">    QString callbackSignatures;</span><br><span class="line">    <span class="keyword">int</span> iSigIndex = idx;</span><br><span class="line">    args-&gt;<span class="built_in">SetString</span>(idx++, callbackSignatures.<span class="built_in">toStdWString</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">//序列化参数列表，根据不同的参数类型分别处理</span></span><br><span class="line">    <span class="keyword">for</span> (std::<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; arguments.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsBool</span>()) &#123;</span><br><span class="line">            args-&gt;<span class="built_in">SetBool</span>(idx++, arguments[i]-&gt;<span class="built_in">GetBoolValue</span>());</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsInt</span>()) &#123;</span><br><span class="line">            args-&gt;<span class="built_in">SetInt</span>(idx++, arguments[i]-&gt;<span class="built_in">GetIntValue</span>());</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsDouble</span>()) &#123;</span><br><span class="line">            <span class="keyword">double</span> dValue = arguments[i]-&gt;<span class="built_in">GetDoubleValue</span>();</span><br><span class="line">            <span class="comment">//如果double值为NAN，报错</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">isnan</span>(dValue)) &#123;</span><br><span class="line">                exception = <span class="built_in">QString</span>(<span class="string">u8&quot;argument %1 is nan !&quot;</span>).<span class="built_in">arg</span>(i).<span class="built_in">toStdWString</span>();</span><br><span class="line">                retval = CefV8Value::<span class="built_in">CreateUndefined</span>();</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            args-&gt;<span class="built_in">SetDouble</span>(idx++, dValue);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsString</span>()) &#123;</span><br><span class="line">            args-&gt;<span class="built_in">SetString</span>(idx++, arguments[i]-&gt;<span class="built_in">GetStringValue</span>());</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsFunction</span>()) &#123;</span><br><span class="line">            <span class="comment">//参数类型为函数，生成回调函数签名</span></span><br><span class="line">            ...</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            args-&gt;<span class="built_in">SetNull</span>(idx++);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//处理带返回值的同步调用，略</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 发送IPC消息，尝试读取返回值，异步调用返回undefined</span></span><br><span class="line">    <span class="keyword">if</span> (m_browser &amp;&amp; m_frame) &#123;</span><br><span class="line">        m_frame-&gt;<span class="built_in">SendProcessMessage</span>(PID_BROWSER, msg);</span><br><span class="line">        retval = <span class="built_in">readSynchronizeValue</span>(retTypeSignature, m_metaMethod.retType);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        retval = CefV8Value::<span class="built_in">CreateUndefined</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="7-browser进程收到调用请求后的处理"><a href="#7-browser进程收到调用请求后的处理" class="headerlink" title="7. browser进程收到调用请求后的处理"></a>7. browser进程收到调用请求后的处理</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefBrowserHandlerBase::DispatchNotifyRequest</span><span class="params">(CefRefPtr&lt;CefBrowser&gt; browser,</span></span></span><br><span class="line"><span class="params"><span class="function">    CefProcessId source_process,</span></span></span><br><span class="line"><span class="params"><span class="function">    CefRefPtr&lt;CefProcessMessage&gt; message)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    CefString messageName = message-&gt;<span class="built_in">GetName</span>();</span><br><span class="line">    CefRefPtr&lt;CefListValue&gt; messageArguments = message-&gt;<span class="built_in">GetArgumentList</span>();</span><br><span class="line">    <span class="keyword">if</span> (!messageArguments)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> browserId = browser-&gt;<span class="built_in">GetIdentifier</span>();</span><br><span class="line">    <span class="keyword">if</span> (messageName == QCEF_INVOKENGLMETHOD) &#123;</span><br><span class="line">        QVariantList varList;</span><br><span class="line">        <span class="comment">//遍历参数，将CEF参数列表转换为QVariantList</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; messageArguments-&gt;<span class="built_in">GetSize</span>(); i++) &#123;</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> idx = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (QVariant::Type::String != varList[idx].<span class="built_in">type</span>() ||</span><br><span class="line">            QVariant::Type::String != varList[idx + <span class="number">1</span>].<span class="built_in">type</span>()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//获取browserId，判断是否需要处理此消息</span></span><br><span class="line">        <span class="comment">//browser和render是一对多的关系，当收到QCEF_INVOKENGLMETHOD消息时，需要根据browserId进行过滤，否则会重复调用</span></span><br><span class="line">        <span class="keyword">int</span> messageBrowserId = QString::<span class="built_in">fromStdString</span>(varList[idx++].<span class="built_in">toString</span>().<span class="built_in">toStdString</span>()).<span class="built_in">toInt</span>();</span><br><span class="line">        int64 frameId = QString::<span class="built_in">fromStdString</span>(varList[idx++].<span class="built_in">toString</span>().<span class="built_in">toStdString</span>()).<span class="built_in">toLongLong</span>();</span><br><span class="line">        <span class="keyword">if</span> (messageBrowserId != browserId)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//执行反射调用</span></span><br><span class="line">        QString strCallbackSignatures;</span><br><span class="line">        <span class="keyword">bool</span> bOk = QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">inovkeMethod</span>(browserId, varList, strCallbackSignatures);</span><br><span class="line">        <span class="comment">//调用失败，发送清理回调消息</span></span><br><span class="line">        <span class="keyword">if</span> (!bOk) &#123;</span><br><span class="line">            <span class="comment">//send clear callbacks</span></span><br><span class="line">            CefRefPtr&lt;CefProcessMessage&gt; msg = CefProcessMessage::<span class="built_in">Create</span>(QCEF_CLEARNGLCALLBACKS);</span><br><span class="line">            <span class="keyword">auto</span> paramValue = msg-&gt;<span class="built_in">GetArgumentList</span>();</span><br><span class="line">            <span class="keyword">int</span> idx = <span class="number">0</span>;</span><br><span class="line">            paramValue-&gt;<span class="built_in">SetString</span>(idx++, strCallbackSignatures.<span class="built_in">toStdWString</span>());</span><br><span class="line">            browser-&gt;<span class="built_in">GetMainFrame</span>()-&gt;<span class="built_in">SendProcessMessage</span>(CefProcessId::PID_RENDERER, msg);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> bOk;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (...) &#123;</span><br><span class="line">      ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="8-Qt反射调用"><a href="#8-Qt反射调用" class="headerlink" title="8 Qt反射调用"></a>8 Qt反射调用</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefJavaScriptEngine::inovkeMethod</span><span class="params">(<span class="keyword">int</span> browserId, <span class="keyword">const</span> QVariantList&amp; messageArguments, QString&amp; callbackSignature)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> messageBrowserId = QString::<span class="built_in">fromStdString</span>(messageArguments[idx++].<span class="built_in">toString</span>().<span class="built_in">toStdString</span>()).<span class="built_in">toInt</span>();</span><br><span class="line">    qint64 frameId = QString::<span class="built_in">fromStdString</span>(messageArguments[idx++].<span class="built_in">toString</span>().<span class="built_in">toStdString</span>()).<span class="built_in">toLongLong</span>();</span><br><span class="line">    <span class="comment">//校验browserId</span></span><br><span class="line">    <span class="keyword">if</span> (messageBrowserId != browserId) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//校验消息格式</span></span><br><span class="line">    <span class="comment">//browserId className method methodSignature</span></span><br><span class="line">    <span class="keyword">if</span> (QVariant::Type::String != messageArguments[idx].<span class="built_in">type</span>() ||</span><br><span class="line">        QVariant::Type::String != messageArguments[idx + <span class="number">1</span>].<span class="built_in">type</span>() ||</span><br><span class="line">        QVariant::Type::String != messageArguments[idx + <span class="number">2</span>].<span class="built_in">type</span>() ||</span><br><span class="line">        QVariant::Type::String != messageArguments[idx + <span class="number">3</span>].<span class="built_in">type</span>())</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//根据IPC消息中的类名获取元对象实例</span></span><br><span class="line">    <span class="keyword">if</span> (!m_registeredMetaObjectMap.<span class="built_in">contains</span>(className)) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;meta method for name: %s not found !&quot;</span>, <span class="built_in">qPrintable</span>(className));</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> QMetaObject* metaObj = m_registeredMetaObjectMap[className];</span><br><span class="line">    <span class="comment">//校验元对象中是否存在此函数</span></span><br><span class="line">    <span class="keyword">int</span> iMethod = metaObj-&gt;<span class="built_in">indexOfMethod</span>(metaObj-&gt;<span class="built_in">normalizedSignature</span>(methodSignature.<span class="built_in">toStdString</span>().<span class="built_in">c_str</span>()).<span class="built_in">toStdString</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">if</span> (iMethod == <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;method %s index not found !&quot;</span>, <span class="built_in">qPrintable</span>(method));</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//获取元函数信息，以此为基础准备反射调用参数，对JS传过来的参数做适当的转换</span></span><br><span class="line">    QMetaMethod metaMethod = m_registeredMetaObjectMap[className]-&gt;<span class="built_in">method</span>(iMethod);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//查找注册对象实例，后续在此对象上执行反射调用</span></span><br><span class="line">    <span class="keyword">if</span> (!m_jsObjectBindingMap.<span class="built_in">contains</span>(className)) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;class name: %s not found !&quot;</span>, <span class="built_in">qPrintable</span>(className));</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    QObject* obj = m_jsObjectBindingMap[className];</span><br><span class="line">    <span class="keyword">bool</span> bNeedWrap = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">//参数转换，会做类型兼容处理，如果参数数量不够则补齐，多余的参数则忽略</span></span><br><span class="line">    QVariantList varList;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt; metaMethod.<span class="built_in">parameterCount</span>(); i++) &#123;</span><br><span class="line">        <span class="keyword">int</span> paramTypeId = metaMethod.<span class="built_in">parameterType</span>(i);</span><br><span class="line">        <span class="keyword">int</span> messageIndex = idx + i;</span><br><span class="line">        <span class="comment">//转换和准备反射调用参数</span></span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">bool</span> bRet = <span class="literal">false</span>;</span><br><span class="line">    QGenericReturnArgument retArg;</span><br><span class="line">    <span class="comment">//使用Qt的反射调用接口发起反射调用</span></span><br><span class="line">    bRet = QMetaObject::<span class="built_in">invokeMethod</span>(obj, <span class="built_in">qUtf8Printable</span>(method), Qt::DirectConnection, retArg, argList[<span class="number">0</span>],</span><br><span class="line">        argList[<span class="number">1</span>], argList[<span class="number">2</span>], argList[<span class="number">3</span>], argList[<span class="number">4</span>], argList[<span class="number">5</span>], argList[<span class="number">6</span>], argList[<span class="number">7</span>], argList[<span class="number">8</span>], argList[<span class="number">9</span>]);</span><br><span class="line">    <span class="keyword">return</span> bRet;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="5-2-回调函数触发机制"><a href="#5-2-回调函数触发机制" class="headerlink" title="5.2 回调函数触发机制"></a>5.2 回调函数触发机制</h2><p>在CEF中，JavaScript函数多为异步调用，通过传递一个callback的方式接收异步调用结果。callback本身是一个V8内的Function对象，它仅在当前render进程上下文中有效，无法通过简单的序列化直接传递到browser进程中，最终callback的触发也是要落回到render进程，那我们应该怎么触发回调呢？<br>所以我们想到了一个经典的解决方案“句柄”。<br>在Win32编程中， <strong>句柄</strong> 的存在非常普遍，大量的Win32 API都使用它作为参数，如WriteFile, CloseHandle，WaitForSingleObject等。句柄其实是内核句柄表的一个标识，使用它可以引用一个内核资源。这样内核就在不暴露内部资源本身的情况下允许用户操作该资源。<br>我们借用这个思路，当一个JavaScript函数被执行时，将参数传入的Function对象记录下来，为该对象生成一个唯一的key，将key作为参数通过IPC发送到browser进程，browser进程在合适的时机通过这个key发送IPC消息给render进程来触发回调。</p>
<h3 id="1-Execute函数中对回调函数的处理"><a href="#1-Execute函数中对回调函数的处理" class="headerlink" title="1 Execute函数中对回调函数的处理"></a>1 Execute函数中对回调函数的处理</h3><p>生成signature，将对象以以signature为key保存起来，这里考虑到一个函数中可以接收多个回调，最后拼接的signature以;分隔。</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefFunctionObject::Execute</span><span class="params">(<span class="keyword">const</span> CefString&amp; name, CefRefPtr&lt;CefV8Value&gt; object,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">const</span> CefV8ValueList&amp; arguments, CefRefPtr&lt;CefV8Value&gt;&amp; retval, CefString&amp; exception)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//序列化参数列表，根据不同的参数类型分别处理</span></span><br><span class="line">    <span class="keyword">for</span> (std::<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; arguments.<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsBool</span>()) &#123;</span><br><span class="line">            ...</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (arguments[i]-&gt;<span class="built_in">IsFunction</span>()) &#123;</span><br><span class="line">            <span class="comment">//参数类型为函数，生成回调函数签名</span></span><br><span class="line">            QString strUuid;</span><br><span class="line">            strUuid = QUuid::<span class="built_in">createUuid</span>().<span class="built_in">toString</span>().<span class="built_in">toUpper</span>();</span><br><span class="line">            strUuid = strUuid.<span class="built_in">mid</span>(<span class="number">1</span>, strUuid.<span class="built_in">size</span>() - <span class="number">2</span>);</span><br><span class="line">            strUuid = strUuid.<span class="built_in">replace</span>(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">            <span class="comment">//生成格式为：browserId.frameId.className.methodName.index.uuid</span></span><br><span class="line">            QString callbackSig = <span class="built_in">QString</span>(<span class="string">&quot;%1.%2.%3.%4.%5.%6&quot;</span>).<span class="built_in">arg</span>(m_browser-&gt;<span class="built_in">GetIdentifier</span>()).<span class="built_in">arg</span>(frameId).<span class="built_in">arg</span>(m_metaMethod.className).<span class="built_in">arg</span>(m_metaMethod.name)</span><br><span class="line">                .<span class="built_in">arg</span>(i).<span class="built_in">arg</span>(strUuid);</span><br><span class="line">            signatureList &lt;&lt; callbackSig;</span><br><span class="line">            <span class="comment">//保存当前的callback和contex</span></span><br><span class="line">            QCefFunctionCallback functionCallback;</span><br><span class="line">            functionCallback.callback = arguments[i];</span><br><span class="line">            functionCallback.context = CefV8Context::<span class="built_in">GetCurrentContext</span>();</span><br><span class="line">            <span class="comment">//以signature为key，插入到map中</span></span><br><span class="line">            CefString callbackSignature = callbackSig.<span class="built_in">toStdWString</span>();</span><br><span class="line">            m_callbacksMap.<span class="built_in">insert</span>(callbackSignature, functionCallback);</span><br><span class="line">            <span class="built_in">TRACED</span>(<span class="string">&quot;callback found at: %d, signature is: %s&quot;</span>, i, <span class="built_in">qPrintable</span>(callbackSig));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            args-&gt;<span class="built_in">SetNull</span>(idx++);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//处理带返回值的同步调用，略</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 发送IPC消息，尝试读取返回值，异步调用返回undefined</span></span><br><span class="line">    <span class="keyword">if</span> (m_browser &amp;&amp; m_frame) &#123;</span><br><span class="line">        m_frame-&gt;<span class="built_in">SendProcessMessage</span>(PID_BROWSER, msg);</span><br><span class="line">        retval = <span class="built_in">readSynchronizeValue</span>(retTypeSignature, m_metaMethod.retType);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        retval = CefV8Value::<span class="built_in">CreateUndefined</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="2-browser进程IPC触发回调"><a href="#2-browser进程IPC触发回调" class="headerlink" title="2 browser进程IPC触发回调"></a>2 browser进程IPC触发回调</h3><p>browser进程中，当函数执行结束后，拼接IPC消息发送给render进程用来触发回调</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefCoreBrowserBase::invokeJavaScriptCallback</span><span class="params">(qint64 frameId, <span class="keyword">const</span> QString&amp; jsCallbackSignature, QVariantList params)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!_browser)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    CefRefPtr&lt;CefProcessMessage&gt; msg = CefProcessMessage::<span class="built_in">Create</span>(QCEF_INVOKENGLCALLBACK);</span><br><span class="line">    <span class="keyword">auto</span> paramValue = msg-&gt;<span class="built_in">GetArgumentList</span>();</span><br><span class="line">    <span class="keyword">int</span> idx = <span class="number">0</span>;</span><br><span class="line">    paramValue-&gt;<span class="built_in">SetString</span>(idx++, jsCallbackSignature.<span class="built_in">toStdWString</span>());</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> <span class="keyword">auto</span>&amp; value : params) &#123;</span><br><span class="line">        QVariant::Type vType = value.<span class="built_in">type</span>();</span><br><span class="line">        <span class="keyword">if</span> (vType == QVariant::Type::String) &#123;</span><br><span class="line">            ...</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            paramValue-&gt;<span class="built_in">SetNull</span>(idx);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    CefRefPtr&lt;CefFrame&gt; frame = _browser-&gt;<span class="built_in">GetFrame</span>(frameId);</span><br><span class="line">    <span class="keyword">if</span> (!frame) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;browserId: %d get frame by frameId: %ld failed !&quot;</span>, <span class="built_in">getBrowserId</span>(), frameId);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    frame-&gt;<span class="built_in">SendProcessMessage</span>(CefProcessId::PID_RENDERER, msg);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-render进程响应代码"><a href="#3-render进程响应代码" class="headerlink" title="3 render进程响应代码"></a>3 render进程响应代码</h3><p>render进程收到回调触发的IPC消息后，通过signatur中的信息查找到对应的回调函数并触发该回调</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">RenderDelegate::OnTriggerEventNotifyMessage</span><span class="params">(CefRefPtr&lt;CefBrowser&gt; browser,</span></span></span><br><span class="line"><span class="params"><span class="function">            CefRefPtr&lt;CefFrame&gt; frame,</span></span></span><br><span class="line"><span class="params"><span class="function">            CefProcessId source_process,</span></span></span><br><span class="line"><span class="params"><span class="function">            CefRefPtr&lt;CefProcessMessage&gt; message)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="built_in">TRACET</span>();</span><br><span class="line">        CefString messageName = message-&gt;<span class="built_in">GetName</span>();</span><br><span class="line">        <span class="keyword">if</span> (messageName == QCEF_INVOKENGLCALLBACK) &#123;</span><br><span class="line">            CefRefPtr&lt;CefListValue&gt; messageArguments = message-&gt;<span class="built_in">GetArgumentList</span>();</span><br><span class="line">            ...</span><br><span class="line">            strSignature = QString::<span class="built_in">fromStdWString</span>(messageArguments-&gt;<span class="built_in">GetString</span>(idx++).<span class="built_in">ToWString</span>());</span><br><span class="line">            <span class="keyword">auto</span> sigList = strSignature.<span class="built_in">split</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">int</span> browserId = sigList[<span class="number">0</span>].<span class="built_in">toInt</span>();</span><br><span class="line">            int64 frameId = sigList[<span class="number">1</span>].<span class="built_in">toLongLong</span>();</span><br><span class="line">            <span class="comment">//not the same browser, return it.</span></span><br><span class="line">            <span class="keyword">if</span> (browserId != browser-&gt;<span class="built_in">GetIdentifier</span>())</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            CefRefPtr&lt;CefListValue&gt; newArguments = CefListValue::<span class="built_in">Create</span>();</span><br><span class="line">            <span class="keyword">int</span> iNewIdx = <span class="number">0</span>;</span><br><span class="line">            <span class="comment">//准备回调函数的参数</span></span><br><span class="line">            <span class="keyword">for</span> (idx; idx &lt; messageArguments-&gt;<span class="built_in">GetSize</span>(); idx++) &#123;</span><br><span class="line">                newArguments-&gt;<span class="built_in">SetValue</span>(iNewIdx++, messageArguments-&gt;<span class="built_in">GetValue</span>(idx));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">auto</span> it = frame_id_to_client_map_.<span class="built_in">find</span>(frameId);</span><br><span class="line">            <span class="keyword">if</span> (it != frame_id_to_client_map_.<span class="built_in">end</span>()) &#123;</span><br><span class="line">                it-&gt;second-&gt;<span class="built_in">invokeCallBack</span>(strSignature, newArguments);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="built_in">TRACEE</span>(<span class="string">&quot;QCEF_INVOKENGLCALLBACK can&#x27;t find QCefClient by id: %ld&quot;</span>, frameId);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (messageName == QCEF_CLEARNGLCALLBACKS) &#123;</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="4-执行回调"><a href="#4-执行回调" class="headerlink" title="4. 执行回调"></a>4. 执行回调</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefJavaScriptEnvironment::invokeCallBack</span><span class="params">(<span class="keyword">const</span> QString&amp; signature, CefRefPtr&lt;CefListValue&gt; argumentList)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">TRACED</span>(<span class="string">&quot;signature is: %s&quot;</span>, <span class="built_in">qPrintable</span>(signature));</span><br><span class="line">    <span class="keyword">auto</span> sigList = signature.<span class="built_in">split</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (sigList.<span class="built_in">size</span>() != SIGNATURE_VALID_PARTS_COUNT)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">int</span> browserId = sigList[<span class="number">0</span>].<span class="built_in">toInt</span>();</span><br><span class="line">    <span class="keyword">int</span> frameId = sigList[<span class="number">1</span>].<span class="built_in">toInt</span>();</span><br><span class="line">    <span class="keyword">const</span> QString&amp; className = sigList[<span class="number">2</span>];</span><br><span class="line">    <span class="keyword">const</span> QString&amp; method = sigList[<span class="number">3</span>];</span><br><span class="line">    <span class="comment">//根据className查找JavaScript对象</span></span><br><span class="line">    <span class="keyword">if</span> (!m_javaScriptObjectMap.<span class="built_in">contains</span>(className)) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;js object map not found %s&quot;</span>, <span class="built_in">qPrintable</span>(className));</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    QPair&lt;CefRefPtr&lt;QCefJavaScriptObject&gt;, CefRefPtr&lt;CefV8Value&gt;&gt; jsObjectPair = m_javaScriptObjectMap[className];</span><br><span class="line">    <span class="keyword">if</span> (!jsObjectPair.first || !jsObjectPair.second) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;%s: jsObjectPair invalid !&quot;</span>, <span class="built_in">qPrintable</span>(className));</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//根据函数名查找函数对象</span></span><br><span class="line">    CefRefPtr&lt;QCefFunctionObject&gt; functionObj = jsObjectPair.first-&gt;<span class="built_in">getFunction</span>(method);</span><br><span class="line">    <span class="keyword">if</span> (!functionObj) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;get functionObj failed !&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    CefRefPtr&lt;CefV8Value&gt; retVal;</span><br><span class="line">    CefString exception;</span><br><span class="line">    <span class="comment">//触发回调</span></span><br><span class="line">    <span class="keyword">bool</span> bRet = functionObj-&gt;<span class="built_in">ExecuteCallback</span>(signature.<span class="built_in">toStdWString</span>(), jsObjectPair.second, argumentList, retVal, exception);</span><br><span class="line">    <span class="keyword">if</span> (bRet)</span><br><span class="line">        <span class="built_in">TRACED</span>(<span class="string">&quot;execute callback using signature: %s success !&quot;</span>, <span class="built_in">qPrintable</span>(signature));</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;execute callback using signature: %s failed !!!&quot;</span>, <span class="built_in">qPrintable</span>(signature));</span><br><span class="line">    <span class="keyword">return</span> bRet;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">QCefFunctionObject::ExecuteCallback</span><span class="params">(<span class="keyword">const</span> CefString&amp; signature, CefRefPtr&lt;CefV8Value&gt; object, CefRefPtr&lt;CefListValue&gt; arguments, CefRefPtr&lt;CefV8Value&gt;&amp; retval, CefString&amp; exception)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//判断当前callback是否存在</span></span><br><span class="line">    <span class="keyword">if</span> (!m_callbacksMap.<span class="built_in">contains</span>(signature)) &#123;</span><br><span class="line">        exception = <span class="string">&quot;can&#x27;t get callback !&quot;</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//获取之前保存的callback结构，切换context</span></span><br><span class="line">    QCefFunctionCallback funcCallback = m_callbacksMap[signature];</span><br><span class="line">    <span class="keyword">if</span> (!funcCallback.context-&gt;<span class="built_in">Enter</span>()) &#123;</span><br><span class="line">        exception = <span class="string">L&quot;enter current context error !&quot;</span>;</span><br><span class="line">        retval = CefV8Value::<span class="built_in">CreateUndefined</span>();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function">CefV8ValueList <span class="title">v8Arguments</span><span class="params">(arguments-&gt;GetSize())</span></span>;</span><br><span class="line">    <span class="keyword">if</span> (arguments) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; arguments-&gt;<span class="built_in">GetSize</span>(); i++) &#123;</span><br><span class="line">            <span class="comment">//参数转换</span></span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//调用CEF标准接口触发函数执行 CefV8Value::ExecuteFunction</span></span><br><span class="line">    retval = funcCallback.callback-&gt;<span class="built_in">ExecuteFunction</span>(object, v8Arguments);</span><br><span class="line">    <span class="comment">//退出上下文环境</span></span><br><span class="line">    <span class="keyword">if</span> (!funcCallback.context-&gt;<span class="built_in">Exit</span>()) &#123;</span><br><span class="line">        exception = <span class="string">L&quot;exit current context error !&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="5-3-回调资源的管理"><a href="#5-3-回调资源的管理" class="headerlink" title="5.3 回调资源的管理"></a>5.3 回调资源的管理</h2><p>当我们使用句柄的时候，记得一定要调用CloseHandle关闭打开的句柄资源，否则会导致句柄泄露。回顾我们对回调函数的处理过程，回调函数也需要在合适的实际被释放，否则同样会造成泄露。CEF内部对象统一使用引用计数来管理，我们将回调保存在map中会导致该对象无法释放，这样是存在问题的。<br>那思考这样一个问题：<strong>一个JavaScript回调函数的生命周期是怎样的？</strong><br>一个简单的思路是，回调函数执行完之后立即销毁回调对象，这样我们可以保证回调函数最终会被释放。 但是这样做是否恰当？当函数执行结束后，回调函数是否还可能继续存在？<br>在Launcher中存在这样一个接口调用：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nts.stub.setCallback(callback);</span><br></pre></td></tr></table></figure>
<p>这里要求web端注册一个回调，当stub的通知消息到达时，此回调会被触发，将通知消息透传给web。这时注册进去的callback更类似一个全局变量，可以被重复触发。<br>所以实际的情况是，回调函数在调用结束后，可能会继续存在，也可能被销毁，这个要看具体的业务需求，而业务存在于browser进程。<br>所以回调函数的释放时机：</p>
<ol>
<li>函数执行完之后自动释放</li>
<li><strong>由browser进程决定何时释放</strong></li>
</ol>
<p>我们选方案2</p>
<p>简单的思路，添加一种专门用于释放回调函数的IPC消息，在恰当的时机发送给render进程，专门用来释放回调资源。但是这样存在一个问题，如果使用者忘了发送清理消息，callback仍然会泄露。本着<strong>避免使用者出错</strong>的原则，我们不希望用户关心释放的细节，这里使用C++的RAII对回调函数进行管理。</p>
<blockquote>
<p>RAII（<strong>R</strong>esource <strong>A</strong>cquisition <strong>I</strong>s <strong>I</strong>nitialization）,也称为“资源获取就是初始化”，是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下，已构造的对象最终会销毁，即它的析构函数最终会被调用。简单的说，RAII 的做法是使用一个对象，在其构造时获取资源，在对象生命期控制对资源的访问使之始终保持有效，最后在对象析构的时候释放资源。</p>
</blockquote>
<p>我们将callback的signature放如callBack对象中进行管理，通过引用计数来管理此对象的生命周期，直到不存在指针指向此对象，则销毁它，析构函数中发送释放回调函数的消息，从render进程中清理掉此回调。</p>
<p>实现代码如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QCEFCORE_EXPORT</span> <span class="title">JavaScriptCallback</span> &#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">JavaScriptCallback</span>(<span class="keyword">const</span> QString&amp; signature, class QCefCoreManagerBase* coreManager);</span><br><span class="line">    <span class="built_in">JavaScriptCallback</span>();</span><br><span class="line">    ~<span class="built_in">JavaScriptCallback</span>();</span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">isValid</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">trigger</span><span class="params">(<span class="keyword">const</span> QVariantList&amp; vars)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">getBrowserId</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function">qint64 <span class="title">getFrameId</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    QString m_callbackSignature;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//实现代码</span></span><br><span class="line">JavaScriptCallback::~<span class="built_in">JavaScriptCallback</span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (m_callbackSignature.<span class="built_in">isEmpty</span>() || !m_coreManager)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    m_coreManager-&gt;<span class="built_in">clearJavaScriptCallback</span>(m_callbackSignature);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">JavaScriptCallback::isValid</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> !m_callbackSignature.<span class="built_in">isEmpty</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">JavaScriptCallback::clear</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    m_callbackSignature.<span class="built_in">clear</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">JavaScriptCallback::trigger</span><span class="params">(<span class="keyword">const</span> QVariantList&amp; vars)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (m_callbackSignature.<span class="built_in">isEmpty</span>() || !m_coreManager)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    m_coreManager-&gt;<span class="built_in">invokeJavaScriptCallback</span>(m_callbackSignature, vars);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>因为回调函数可能存在多个，所以我们构造了一个JavaScriptCallbacksCollection对象用来管理这些回调函数，它会接收形如：sig1;sig2;sig3;的字符串，拆分并依次构造回调对象，保存在内部的对象列表中。此对象是每一个C++导出函数的第一个参数，下面的绑定示例代码中演示了此对象的使用。当通过反射调用时，此对象会被构造并传递给C++函数。</p>
<h2 id="5-4-简单使用"><a href="#5-4-简单使用" class="headerlink" title="5.4 简单使用"></a>5.4 简单使用</h2><p>自动绑定层实现完成之后，使用是非常方便的，仅需要定义要导出的接口，然后注册即可，新增接口时直接添加新的C++函数，无需其它繁琐步骤即可自动注到JavaScript中。</p>
<h3 id="1-定义QObject的子类，通过Qt宏将要导出的C-接口定义为槽函数"><a href="#1-定义QObject的子类，通过Qt宏将要导出的C-接口定义为槽函数" class="headerlink" title="1 定义QObject的子类，通过Qt宏将要导出的C++接口定义为槽函数"></a>1 定义QObject的子类，通过Qt宏将要导出的C++接口定义为槽函数</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JSObjectBase</span> :</span> <span class="keyword">public</span> QObject &#123;</span><br><span class="line">    Q_OBJECT</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">JSObjectBase</span>(QObject* parent);</span><br><span class="line"><span class="keyword">public</span> slots:</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    1.getWebId——获取窗口的ID</span></span><br><span class="line"><span class="comment">    function signature	nts.base.getWebId(ResultCallback callback)</span></span><br><span class="line"><span class="comment">    parameters	参数名	参数类型	参数说明</span></span><br><span class="line"><span class="comment">    callback</span></span><br><span class="line"><span class="comment">    response	参数名	参数类型	参数说明</span></span><br><span class="line"><span class="comment">        id	string	该页面的唯一标识</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">getWebId</span><span class="params">(<span class="keyword">const</span> JavaScriptCallbacksCollection&amp; cbCollection)</span></span>;</span><br><span class="line"></span><br><span class="line">    ....</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//实现代码</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">JSObjectBase::getWebId</span><span class="params">(<span class="keyword">const</span> JavaScriptCallbacksCollection&amp; cbCollections)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//获取第一个回调函数</span></span><br><span class="line">    JavaScriptGetDataCallbackPtr cb = cbCollections.get&lt;JavaScriptGetDataCallback&gt;(<span class="number">0</span>);</span><br><span class="line">    <span class="comment">//获取此回调对应的browserId</span></span><br><span class="line">    <span class="keyword">int</span> browserId = cb-&gt;<span class="built_in">getBrowserId</span>();</span><br><span class="line">    <span class="comment">//根据browserId获取webId</span></span><br><span class="line">    <span class="keyword">const</span> QString&amp; webId = NglLauncher::<span class="built_in">get</span>()-&gt;<span class="built_in">getMainWindow</span>()-&gt;<span class="built_in">getWebId</span>(browserId);</span><br><span class="line">    <span class="comment">//拼接返回json，发送IPC消息触发回调</span></span><br><span class="line">    cb-&gt;<span class="built_in">execute</span>(ResultStatus_Success, <span class="built_in">JsonBuilder</span>().<span class="built_in">add</span>(<span class="string">&quot;id&quot;</span>, webId).<span class="built_in">build</span>(), <span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="comment">//cb对象销毁，发送清理消息</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="2-注册Qt对象"><a href="#2-注册Qt对象" class="headerlink" title="2 注册Qt对象"></a>2 注册Qt对象</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">JavaScriptEngine::init</span><span class="params">(<span class="keyword">const</span> QString&amp; contextId, <span class="keyword">const</span> QString&amp; version, <span class="keyword">int</span> versionCode)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">bool</span> bOk = QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">init</span>();</span><br><span class="line">    <span class="keyword">if</span> (!bOk) &#123;</span><br><span class="line">        <span class="built_in">TRACEE</span>(<span class="string">&quot;QCefJavaScriptEngine init failed !&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;base&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectBase</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;login&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectLogin</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;game&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectGame</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;system&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectSystem</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;system.os&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectOs</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;system.disk&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectDisk</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;system.memory&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectMemory</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;system.cpu&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectCPU</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;system.videoCard&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectVideoCard</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;stub&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectStub</span>(<span class="keyword">this</span>));</span><br><span class="line">    QCefJavaScriptEngine::<span class="built_in">get</span>()-&gt;<span class="built_in">registerObject</span>(<span class="string">&quot;ngl&quot;</span>, <span class="keyword">new</span> <span class="built_in">JSObjectNgl</span>(contextId, version, versionCode, <span class="keyword">this</span>));</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-测试"><a href="#3-测试" class="headerlink" title="3 测试"></a>3 测试</h3><p><img src="/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/IMG_2020-11-30-09-47-26.png"></p>

    </div>

    
    
    

    <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/C/" rel="tag"># C++</a>
              <a href="/tags/CEF/" rel="tag"># CEF</a>
          </div>

        

          <div class="post-nav">
            <div class="post-nav-item">
                <a href="/2018/04/06/%E8%AE%BA%E7%A8%8B%E5%BA%8F%E5%91%98%E4%B8%8E%E6%9C%80%E4%BC%98%E8%A7%A3/" rel="prev" title="论程序员与最优解">
                  <i class="fa fa-chevron-left"></i> 论程序员与最优解
                </a>
            </div>
            <div class="post-nav-item">
                <a href="/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/" rel="next" title="quickjs调试机制实现解析">
                  quickjs调试机制实现解析 <i class="fa fa-chevron-right"></i>
                </a>
            </div>
          </div>
    </footer>
  </article>
</div>






</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Alex Peng</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/pisces/" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/next-boot.js"></script>

  
<script src="/js/third-party/search/local-search.js"></script>




  





</body>
</html>
